﻿using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using NLog;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Stee.CAP8.Communication.HttpInterface
{
    /// <summary>
    /// <p>
    /// Transparent proxy for making HttpClient calls easier, using <see cref="DispatchProxy"/>.
    /// </p>
    /// <p>
    /// To use it, create a interface, annotating it and its methods with the appropriate attributes,
    /// then request a HttpClient proxy for it.
    /// </p> 
    /// </summary>
    /// <typeparam name="T">The type of the interface to implement</typeparam>
    public class HttpClientProxy : DispatchProxy
    {
        #region Static Function
        /// <summary>
        /// Create a new transparent proxy using the specified <see cref="HttpClientOption"/> />.
        /// </summary>
        /// <param name="configurationSection">The developer's <see cref="IConfigurationSection"/> to use.</param>
        /// <returns>The new interface proxy instance implementing the requested interface.</returns>
        public static T CreateInstance<T>(IConfigurationSection configurationSection)
        {
            if (configurationSection == null)
            {
                throw new ArgumentNullException($"Configuration Section can not be null! Object Name: {nameof(configurationSection)}");
            }
            var httpClientOption = new HttpClientOption();
            configurationSection.Bind(httpClientOption);
            if (string.IsNullOrEmpty(httpClientOption.BaseAddress))
            {
                throw new ArgumentNullException($"BaseAddress can not be null! Object Name: {nameof(configurationSection)}");
            }

            IServiceCollection serviceCollection = new ServiceCollection();

            serviceCollection.AddHttpClient(typeof(T).Name, httpClient =>
            {
                httpClient.BaseAddress = new Uri(httpClientOption.BaseAddress);
                httpClient.Timeout = TimeSpan.FromSeconds(httpClientOption.TimeOut);
            }).ConfigurePrimaryHttpMessageHandler(() =>
            {
                return new HttpClientHandler()
                {
                    ClientCertificateOptions = ClientCertificateOption.Automatic,
                    ServerCertificateCustomValidationCallback = (message, cert, chain, error) =>
                    {
                        switch (httpClientOption.HttpsCertCheck)
                        {
                            case CertCheckType.CheckCertIssuer:
                                if (!string.IsNullOrEmpty(httpClientOption.CertIssuer))
                                {
                                    return cert.Issuer.Equals(httpClientOption.CertIssuer, StringComparison.OrdinalIgnoreCase);
                                }
                                return false;
                            default:
                                return true;
                        }
                    }
                };
            });

            var provider = serviceCollection.BuildServiceProvider();

            var httpClientProxy = DispatchProxy.Create<T, HttpClientProxy>();
            var contractAttr = typeof(T).GetCustomAttribute<HttpContractAttribute>()
                                                 ?? throw new ArgumentException($"Contract type must be annotated with {typeof(HttpContractAttribute).Name}.");

            (httpClientProxy as HttpClientProxy).InitialInstance(provider.GetService<IHttpClientFactory>(), contractAttr.Namespace);
            
            LogHelper.WriteLog(LogLevel.Info, $"HttpClientProxy static create instance: {typeof(T).Name}");

            return httpClientProxy;
        }
        #endregion

        #region Initial Object
        private readonly Encoding _encoding = Encoding.UTF8;
        private readonly string _contentEncoding = "application/json";
        private readonly StringComparison _stringComparison = StringComparison.CurrentCulture;
        private readonly string _requestIDKey = "x-requestid";

        private string _namespace;
        private JsonSerializer _jsonSerializer;
        private CancellationToken _cancellationToken;
        private IHttpClientFactory _httpClientFactory;
        private Action<HttpResponseMessage> _responsePostProcess;

        private string _token;
        private string _sessionID;
        private bool _retryFlag;
        private Dictionary<string, string> _dicClientID;

        /// <summary>
        /// Initial some parameters when create a new transparent proxy using the specified <see cref="IHttpClientFactory"/> and so/>.
        /// </summary>
        /// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/> to use.</param>
        /// <param name="Namespace">The Contract type get from deverloper interface attribute.</param>
        internal void InitialInstance(object httpClientFactory, object Namespace)
        {
            _httpClientFactory = (IHttpClientFactory)httpClientFactory;
            _namespace = (string)Namespace;
            _jsonSerializer = new JsonSerializer();
            _responsePostProcess = ((HttpResponseMessage response) => response.EnsureSuccessStatusCode());
            _dicClientID = new Dictionary<string, string>();
        }
        #endregion

        #region IHttpClientProxyBase Function
        /// <summary>
        /// execute the function of <see cref="IHttpClientProxyBase"/>
        /// </summary>
        /// <param name="methodInfo">The attributes of method</param>
        /// <param name="args">The parameters of method</param>
        /// <returns></returns>
        private object DoIHttpClientProxyBaseFun(MethodInfo targetMethod, object[] args)
        {
            switch (targetMethod.Name)
            {
                case "SetToken":
                    _token = (string)args[0];
                    break;
                case "SetSessionID":
                    _sessionID= (string)args[0];
                    break;
                case "SetRetryFlag":
                    _retryFlag = true;
                    break;
            }
            return null;
        }
        #endregion

        #region Main function
        /// <summary>
        /// The entry function of HttpClient Proxy
        /// </summary>
        /// <param name="methodInfo"><see cref="MethodInfo"/></param>
        /// <param name="args">The parameters of method</param>
        /// <returns>object</returns>
        protected override object Invoke(MethodInfo methodInfo, object[] args)
        {
            if (methodInfo == null || args == null)
            {
                throw new InvalidOperationException("no HttpCallAttribute on method " + typeof(MethodInfo));
            }

            //Execute IHttpClientProxyBase function
            if (methodInfo.DeclaringType.Name == "IHttpClientProxyBase")
            {
                return DoIHttpClientProxyBaseFun(methodInfo, args);
            }
            else //Call HttpClient function
            {
                LogHelper.WriteLog(LogLevel.Info, $"Http Request Start, Method: {methodInfo.DeclaringType.FullName}.{methodInfo.Name}.");
#pragma warning disable CA2000 // 丢失范围之前释放对象
                var request = PrepareHttpRequest(methodInfo, args);
                object ret = ExecuteHttpRequest(request, methodInfo);
                LogHelper.WriteLog(LogLevel.Info, $"Http Request End  , Result: {ret}.");
                return ret;
            }
        }

        /// <summary>
        /// The entry function of HttpClient Proxy
        /// </summary>
        /// <param name="methodInfo"><see cref="MethodInfo"/></param>
        /// <param name="args">The parameters of method</param>
        /// <returns><see cref="HttpRequestMessage"/></returns>
        private HttpRequestMessage PrepareHttpRequest(MethodInfo methodInfo, object[] args)
        {
            var attr = methodInfo.GetCustomAttribute<HttpCallAttribute>() ?? throw new InvalidOperationException("No HttpCallAttribute on method: " + methodInfo.Name);

            var path = attr.Path;
            var headers = new Dictionary<string, string>();
            var queryParams = new Dictionary<string, object>();
            var bodyParams = new Dictionary<string, object>();

            _cancellationToken = CancellationToken.None;

            #region gather parameters

            // go through method parameters
            var methodParameters = methodInfo.GetParameters();
            for (var i = 0; i < methodParameters.Length; i++)
            {
                var pi = methodParameters[i];
                if (pi.IsOut)
                {
                    throw new NotImplementedException($"out parameters are not supported. parameter name: {pi.Name}.");
                }
                var arg = args[i];
                // check if parameter is to be included as query parameter
                var queryParamAttr = pi.GetCustomAttribute<QueryParamAttribute>();
                if (queryParamAttr != null)
                {
                    var paramName = queryParamAttr.Name ?? pi.Name;
                    queryParams[paramName] = arg;
                    continue;
                }

                // check if parameter is to be substituted into the url
                var urlParamAttr = pi.GetCustomAttribute<UrlParamAttribute>();
                if (urlParamAttr != null && path != null)
                {
                    var paramName = urlParamAttr.Name ?? pi.Name;
                    path = path.Replace("{" + paramName + "}", ConvertInput(arg));
                    continue;
                }

                var headerAttr = pi.GetCustomAttribute<HeaderAttribute>();
                if (headerAttr != null)
                {
                    //request.Headers.Add(headerAttr.Name, Utility.ConvertInput(arg));
                    headers.Add(headerAttr.Name, ConvertInput(arg));
                    continue;
                }

                var bodyAttr = pi.GetCustomAttribute<BodyAttribute>();
                if (bodyAttr != null)
                {
                    var paramName = bodyAttr.Name ?? pi.Name;
                    bodyParams[paramName] = arg;
                    continue;
                }

                if (arg is CancellationToken token)
                {
                    _cancellationToken = token;
                    continue;
                }

                // fall back to query paramerer if not specified otherwise
                queryParams[pi.Name] = arg;
            }

            #endregion

            #region prepare request

            //Generate URL
            var uri = CombineUri(queryParams, _namespace, path);

            var request = new HttpRequestMessage(GetHttpMethod(attr.HttpMethod), uri);
            
            //add client Request ID
            SetRequestID(request, $"{methodInfo.DeclaringType.FullName}.{methodInfo.Name}");

            if (attr.LongRunning)
            {
                request.SetTimeout(Timeout.InfiniteTimeSpan);
            }

            //add token
            request.SetToken(_token);
            //add session ID
            request.SetSessionID(_sessionID);

            foreach (var item in headers)
            {
                request.Headers.Add(item.Key, item.Value);
            }
           
            //Add body params
            if (bodyParams.Count == 1 && attr.BodyEncoding == EncodingType.None)
            {
                request.Content = ConvertBodyInput(bodyParams.First().Value);
            }
            else if (bodyParams.Count > 0)
            {
                request.Content = ConvertMultipartBody(bodyParams, attr);
            }

            foreach (var headerAttribute in methodInfo.GetCustomAttributes<AddHeaderAttribute>())
            {
                if (headerAttribute.Values.Length == 1)
                {
                    request.Headers.Add(headerAttribute.Name, headerAttribute.Values[0]);
                }
                else
                {
                    request.Headers.Add(headerAttribute.Name, headerAttribute.Values);
                }
            }

            #endregion

            return request;
        }

        /// <summary>
        /// Execute Http Request and return defined object.
        /// </summary>
        /// <param name="request"><see cref="HttpRequestMessage"/></param>
        /// <param name="methodInfo"><see cref="MethodInfo"/></param>
        /// <returns>object</returns>
        private object ExecuteHttpRequest(HttpRequestMessage request, MethodInfo methodInfo)
        {
            object ret;
            var returnType = methodInfo.ReturnType;
            var httpClient = _httpClientFactory.CreateClient(methodInfo.DeclaringType.Name);
            

            if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))
            {
                returnType = returnType.GetGenericArguments()[0];   // get the function return type from Task<returnType>

                if (returnType == typeof(HttpResponseMessage))
                {
                    ret = NoOpInvoker(httpClient, request, _cancellationToken);
                }
                else if (returnType == typeof(Stream))
                {
                    ret = AsyncStreamInvoker(httpClient, request, _cancellationToken);
                }
                else
                {
                    ret = AsyncInvoker(httpClient, request, returnType, _cancellationToken);
                }
            }
            else if (returnType == typeof(Task))// void async return type
            {
                ret = EmptyTaskInvoker(httpClient, request, _cancellationToken);
            }
            else if (returnType == typeof(Stream))// sync stream type
            {
                ret = SyncStreamInvoker(httpClient, request, _cancellationToken);
            }
            else // sync normal type
            {
                ret = SyncResultInvoker(httpClient, request, returnType, _cancellationToken);
            }

            return ret;

        }

        #endregion

        #region  Execution and response conversion
        /// <summary>
        /// invoke sync http request and return stream type
        /// </summary>
        /// <param name="httpClient"><see cref="HttpClient"/></param>
        /// <param name="request"><see cref="HttpRequestMessage"/></param>
        /// <param name="cancellationToken"><see cref="CancellationToken"/></param>
        /// <returns><see cref="Stream"/></returns>    
        private Stream SyncStreamInvoker(HttpClient httpClient, HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var response = httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken)
                .ConfigureAwait(false).GetAwaiter().GetResult();
            response.EnsureSuccessStatusCode();
            return response.Content.ReadAsStreamAsync()
                .ConfigureAwait(false).GetAwaiter().GetResult();
        }

        /// <summary>
        /// invoke sync http request and return normal type
        /// </summary>
        /// <param name="httpClient"><see cref="HttpClient"/></param>
        /// <param name="request"><see cref="HttpRequestMessage"/></param>
        /// <param name="cancellationToken"><see cref="CancellationToken"/></param>
        /// <returns>a object by <see cref="Type"/></returns>
        private object SyncResultInvoker(HttpClient httpClient, HttpRequestMessage request, Type returnType, CancellationToken cancellationToken)
        {
            var response = httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken)
                .ConfigureAwait(false).GetAwaiter().GetResult();
            response.EnsureSuccessStatusCode();
            var stream = response.Content.ReadAsStreamAsync()
                .ConfigureAwait(false).GetAwaiter().GetResult();
            using (stream)
            {
                using (var reader = new StreamReader(stream))
                {
                    if (returnType == typeof(string))
                    {
                        return reader.ReadToEnd();
                    }
                    else if (returnType == typeof(void))
                    {
                        return null;
                    }
                    else
                    {
                        using (var jsonReader = new JsonTextReader(reader))
                        {
                            return _jsonSerializer.Deserialize(jsonReader, returnType);
                        }
                    }
                }

            }
        }

        /// <summary>
        /// invoke async http request and return empty task type
        /// </summary>
        /// <param name="httpClient"><see cref="HttpClient"/></param>
        /// <param name="request"><see cref="HttpRequestMessage"/></param>
        /// <param name="cancellationToken"><see cref="CancellationToken"/></param>
        /// <returns>Empty Task</returns>
        private Task EmptyTaskInvoker(HttpClient httpClient, HttpRequestMessage request, CancellationToken cancellationToken)
        {
#pragma warning disable CA2008 // 不要在未传递 TaskScheduler 的情况下创建任务
            return httpClient.SendAsync(request, cancellationToken).ContinueWith(task =>
            {
                _responsePostProcess(task.Result);
            }, cancellationToken);
        }

        /// <summary>
        /// invoke async http request and return normal type
        /// </summary>
        /// <param name="httpClient"><see cref="HttpClient"/></param>
        /// <param name="request"><see cref="HttpRequestMessage"/></param>
        /// <param name="returnType"><see cref="Type"/></param>
        /// <param name="cancellationToken"><see cref="CancellationToken"/></param>
        /// <returns>a object by <see cref="Type"/></returns>
        private object AsyncInvoker(HttpClient httpClient, HttpRequestMessage request, Type returnType, CancellationToken cancellationToken)
        {
            var task = httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);

            // otherwise attach a result extractor
            const TaskContinuationOptions opts = TaskContinuationOptions.None;

            var taskParam = Expression.Parameter(typeof(Task<HttpResponseMessage>), "task");    // define lambda parameter
            var resultTaskType = typeof(Task<>).MakeGenericType(returnType);                    // create Task<TResult> type
            var continueWith = ReflectionHelper.GetContinueWith2(resultTaskType);               // get a Task<T>.ContinueWith<U> method

            MethodInfo _convertResponseMethod = typeof(HttpClientProxy).GetMethod(nameof(ConvertResponse));
            var convertResponse = _convertResponseMethod.MakeGenericMethod(returnType);          // make a ConvertResponse<TResult> method


            // compose result extractor task
            var resultTask = continueWith.Invoke(task, new object[] {
                Expression.Lambda(
                    Expression.Call(Expression.Constant(this), convertResponse, taskParam),
                    taskParam).Compile(),
                cancellationToken,
                opts,
                TaskScheduler.Current
            });

            // now unwrap Task<Task<TResult>> to Task<TResult>
            return ReflectionHelper.GetUnwrap(returnType).Invoke(null, new[] { resultTask });
        }

        /// <summary>
        /// contune http request and return steam response
        /// </summary>
        /// <param name="task">a delegate for <see cref="HttpRequestMessage"/></param>
        /// <returns><see cref="Stream"/></returns>
        private async Task<Stream> GetStreamResponse(Task<HttpResponseMessage> task)
        {
            var ret = await task.ConfigureAwait(false);
            _responsePostProcess(ret);
            return await ret.Content.ReadAsStreamAsync().ConfigureAwait(false);
        }

        /// <summary>
        ///  invoke async http request and return <see cref="HttpRequestMessage"/> type
        /// </summary>
        /// <param name="httpClient"><see cref="HttpClient"/></param>
        /// <param name="request"><see cref="HttpRequestMessage"/></param>
        /// <param name="cancellationToken"><see cref="CancellationToken"/></param>
        /// <returns><see cref="HttpResponseMessage"/></returns>
        private Task<HttpResponseMessage> NoOpInvoker(HttpClient httpClient, HttpRequestMessage request, CancellationToken cancellationToken)
        {
            return httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
        }

        /// <summary>
        /// invoke async http request and return <see cref="Stream"/> type
        /// </summary>
        /// <param name="httpClient"><see cref="HttpClient"/></param>
        /// <param name="request"><see cref="HttpRequestMessage"/></param>
        /// <param name="cancellationToken"><see cref="CancellationToken"/></param>
        /// <returns>Stream</returns>
        private Task<Stream> AsyncStreamInvoker(HttpClient httpClient, HttpRequestMessage request, CancellationToken cancellationToken)
        {
#pragma warning disable CA2008 // 不要在未传递 TaskScheduler 的情况下创建任务
            return httpClient
                .SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken)
                .ContinueWith(GetStreamResponse, cancellationToken).Unwrap();
        }

        /// <summary>
        /// Convert the <see cref="HttpResponseMessage"/> to defined type.
        /// </summary>
        /// <typeparam name="TResult">Generic Type</typeparam>
        /// <param name="task">a delegage for <see cref="HttpResponseMessage"/></param>
        /// <returns>Generic Type</returns>
        public async Task<TResult> ConvertResponse<TResult>(Task<HttpResponseMessage> task)
        {
            //Logger.Trace("Convert Response...");
#pragma warning disable CA1062 // 验证公共方法的参数
            var ret = await task.ConfigureAwait(false);
            _responsePostProcess(ret);
            using (var stream = await ret.Content.ReadAsStreamAsync().ConfigureAwait(false))
            {
                using (var reader = new StreamReader(stream))
                {
                    if (typeof(TResult) == typeof(string))
                    {
                        object obj = reader.ReadToEnd();
                        return (TResult)obj;
                    }
                    else
                    {
                        using (var jsonReader = new JsonTextReader(reader))
                        {
                            return (TResult)_jsonSerializer.Deserialize(jsonReader, typeof(TResult));
                        }
                    }
                }
            }
        }
        #endregion

        #region Input conversion methods
        /// <summary>
        /// Convert object to string 
        /// </summary>
        /// <param name="data">source object</param>
        /// <returns>string</returns>
        private string ConvertInput(object data)
        {
            if (data == null) return null;

            if (data.GetType().IsPrimitive)
            {
                return data.ToString();
            }

            if (data is string s) return s;

            using (var writer = new StringWriter())
            {
                _jsonSerializer.Serialize(writer, data);
                var result = writer.ToString();
                return result;
            }
        }

        /// <summary>
        ///  Convert body object to HttpContent 
        /// </summary>
        /// <param name="body">source object</param>
        /// <returns><see cref="HttpContent"/></returns>
        private HttpContent ConvertBodyInput(object body)
        {
            if (body == null) return null;
            switch (body)
            {
                case HttpContent contentBody:
                    return contentBody;
                case Stream streamBody:
                    return new StreamContent(streamBody);
                case byte[] bufferBody:
                    return new ByteArrayContent(bufferBody);
                default:
                    return new StringContent(ConvertInput(body), _encoding, _contentEncoding);
            }
        }
        /// <summary>
        ///  Convert Multipart Body object to HttpContent 
        /// </summary>
        /// <param name="bodyParams"></param>
        /// <param name="attr"></param>
        /// <returns></returns>
        private HttpContent ConvertMultipartBody(IDictionary<string, object> bodyParams, HttpCallAttribute attr)
        {
            switch (attr.BodyEncoding)
            {
                case EncodingType.Multipart:
                    var content = new MultipartContent();
                    foreach (var param in bodyParams) content.Add(ConvertBodyInput(param.Value));
                    return content;
                case EncodingType.MultipartForm:
                    var content2 = new MultipartFormDataContent();
                    foreach (var param in bodyParams)
                    {
                        var p = ConvertBodyInput(param.Value);
                        if (p.Headers.ContentDisposition == null)
                        {
                            p.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { Name = param.Key };
                        }
                        content2.Add(ConvertBodyInput(param.Value), param.Key);
                    }
                    return content2;
                case EncodingType.FormUrlEncoding:
                    return new FormUrlEncodedContent(bodyParams.Select(param => new KeyValuePair<string, string>(param.Key, ConvertInput(param.Value))));
                default:
                    throw new ArgumentException($"multipart body must specify encoding type. Object Name: {attr.BodyEncoding}");
            }
        }

        /// <summary>
        /// combine uri for calling httpclient .
        /// </summary>
        /// <param name="queryParams"></param>
        /// <param name="relative"></param>
        /// <returns></returns>
        private string CombineUri(Dictionary<string, object> queryParams, params string[] relative)
        {
            StringBuilder builder = null;
            foreach (var s in relative)
            {
                if (builder == null)
                {
                    if (!string.IsNullOrEmpty(s))
                    {
                        builder = new StringBuilder(s);
                    }
                }
                else
                {
                    if (!string.IsNullOrEmpty(s))
                    {
                        if (builder.ToString().EndsWith("/", _stringComparison))
                        {
                            if (s.StartsWith("/", _stringComparison))
                            {
                                builder.Append(s.Substring(1));
                            }
                            else
                            {
                                builder.Append(s);
                            }
                        }
                        else
                        {
                            if (s.StartsWith("/", _stringComparison))
                            {
                                builder.Append(s);
                            }
                            else
                            {
                                builder.Append("/" + s);
                            }
                        }
                    }
                }
            }

            if (queryParams.Any())
            {
                builder.Append("&");
                foreach (var pair in queryParams)
                {
                    builder.Append(pair.Key).Append("=").Append(ConvertInput(pair.Value));
                }
            }

            return builder.ToString();
        }

        /// <summary>
        /// Convert HttpVerb to <see cref="HttpMethod"/>
        /// </summary>
        /// <param name="httpVerb"></param>
        /// <returns><see cref="HttpMethod"/></returns>
        private static HttpMethod GetHttpMethod(HttpVerb httpVerb)
        {
            switch (httpVerb)
            {
                case HttpVerb.Get:
                    return HttpMethod.Get;
                case HttpVerb.Put:
                    return HttpMethod.Put;
                case HttpVerb.Post:
                    return HttpMethod.Post;
                case HttpVerb.Delete:
                    return HttpMethod.Delete;
                default:
                    throw new ArgumentOutOfRangeException(nameof(httpVerb), httpVerb, null);
            }
        }
        /// <summary>
        /// set http request id in <see cref="HttpRequestMessage"/>
        /// </summary>
        /// <param name="request"><see cref="HttpRequestMessage"/></param>
        /// <param name="methodName">Invoke method full name</param>
        private void SetRequestID(HttpRequestMessage request, string methodName)
        {
            string requestID, log = $"Http Request Info, Method:{request.Method}, URL:{request.RequestUri}, RetryFlag:{_retryFlag}, ";
            if (_retryFlag)
            {
                if (_dicClientID.ContainsKey(methodName))
                {
                    requestID = _dicClientID[methodName];
                }
                else
                {
                    requestID = Guid.NewGuid().ToString();
                }
                request.Headers.Add(_requestIDKey, requestID);
                _retryFlag = false;
            }
            else
            {
                requestID = Guid.NewGuid().ToString();
                if (_dicClientID.ContainsKey(methodName))
                {
                    _dicClientID[methodName] = requestID;
                }
                else
                {
                    _dicClientID.Add(methodName, requestID);
                }

                request.Headers.Add(_requestIDKey, requestID);
            }
            LogHelper.WriteLog(LogLevel.Debug, $"{log} RequestID:{requestID}.");
        }
        #endregion
    }
}
